import { Button, CCBoolean, CCInteger, Component, Label, Layout, Node, Prefab, ScrollView, UITransform, Vec2, Vec3, _decorator, instantiate } from 'cc';
import { VirtualItem } from './VirtualItem';
const { ccclass, property } = _decorator;

@ccclass('VirtualList')
export class VirtualList extends Component {
    @property(ScrollView)
    scrollView: ScrollView = null!;
    content: Node = null!;

    @property([Prefab])
    itemPrefabs: Prefab[] = [];

    @property(CCBoolean)
    /**是否垂直滑动 */
    vertical: boolean = true;

    /**是否水平滑动 */
    @property(CCBoolean)
    horizontal: boolean = false;

    @property({ type: Layout.Type, tooltip: "布局类型  竖直,水平,网格 " })
    layoutType = Layout.Type.VERTICAL;

    @property(CCInteger)
    paddingLeft: number = 0;

    @property(CCInteger)
    paddingRight: number = 0;

    @property(CCInteger)
    paddingTop: number = 0;

    @property(CCInteger)
    paddingBottom: number = 0;

    @property(CCInteger)
    spacingX: number = 0;

    @property(CCInteger)
    spacingY: number = 0;

    @property({
        type: Layout.VerticalDirection, visible: function (this: VirtualList) {
            return this.vertical;
        }
    })
    verticalDirection = Layout.VerticalDirection.TOP_TO_BOTTOM;

    @property({
        type: Layout.HorizontalDirection, visible: function (this: VirtualList) {
            return this.horizontal;
        }
    })
    horizontalDirection = Layout.HorizontalDirection.LEFT_TO_RIGHT;

    @property({
        type: Boolean, tooltip: "item请继承VirtualItem，暂时只实现竖直非网格排序,其他排序好像没有场景使用,自动适配item大小,如聊天item", visible: function (this: VirtualList) {
            return this.vertical;
        }
    })
    autoSizeItem = false;

    private contentUITransform: UITransform;
    private scrollViewUITransform: UITransform;

    /**
     * 自定义渲染绑定函数 item可以不用挂载组件
     * @param index 数据索引
     * @param node 当前渲染的节点
     */
    public itemRender: (index: number, node: Node) => void = () => { };
    /**
     * 外部绑定的查找 index 的方法  默认0
     */
    public getItemResIndex: (input: any) => number = () => 0;

    /**点击回调绑定 */
    public onItemClick: (index: number, node: Node) => void;

    private dataList: any[] = [];
    private itemMap: Map<number, Node> = new Map();
    private itemPool: Map<number, Node[]> = new Map();
    private itemTypeMap: Map<string, number> = new Map();
    private itemPositions: Vec3[] = [];
    /**item 与预制体不一致时记录下来 */
    private itemSizes: { width: number, height: number }[] = [];
    private columnCount: number = 1;

    private events: EventTouch[] = [];

    onLoad() {
        this.scrollView = this.node.getComponent(ScrollView);
        this.content = this.scrollView.content;
        this.contentUITransform = this.content.getComponent(UITransform);
        this.scrollViewUITransform = this.scrollView.node.getComponent(UITransform);
        if (!this.vertical && !this.horizontal) {
            console.error("请设置水平或竖直滑动")
        }
        this.scrollView.vertical = this.vertical;
        this.scrollView.horizontal = this.horizontal;
        this.scrollView.node.on('scrolling', this.onScrolling, this);
        this.initAnchor();


        this.node.on(Node.EventType.TOUCH_START, this.onTouchHandle, this, true);
        this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchHandle, this, true);
        this.node.on(Node.EventType.TOUCH_END, this.onTouchHandle, this, true);
        this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchHandle, this, true);
    }

    private onTouchHandle(event: EventTouch) {
        //@ts-ignore
        if (event.sham || event.simulate || event.target === this.node) return;

        const cancelEvent: EventTouch = new EventTouch(event.getTouches(), event.bubbles, event.getType());
        cancelEvent.type = event.type;
        cancelEvent.touch = event.touch;
        //@ts-ignore
        cancelEvent.sham = true;
        // 问：这里为啥不直接dispatchEvent
        // 答：必须让ScrollView把真的event先消耗掉，我们再发射假的才可以，
        // 可以去CCNode.js下找一个_doDispatchEvent函数，里面用到了_cachedArray这么个全局变量，
        // 先发射假的话，真的那个数据就被清空了
        this.events.push(cancelEvent);
    }

    update() {
        if (this.events.length === 0) return;
        for (let index = 0; index < this.events.length; index++) {
            this.node.dispatchEvent(this.events[index]);
        }
        this.events.length = 0;
    }

    /**初始化content 锚点  也可以在属性面板手动设置 */
    private initAnchor() {
        if (this.vertical) {
            this.contentUITransform.anchorX = 0.5;
            if (this.verticalDirection == Layout.VerticalDirection.TOP_TO_BOTTOM) {
                this.contentUITransform.anchorY = 1;
            } else {
                this.contentUITransform.anchorY = 0;
            }
        }
        else if (this.horizontal) {
            this.contentUITransform.anchorY = 0.5;
            if (this.horizontalDirection == Layout.HorizontalDirection.LEFT_TO_RIGHT) {
                this.contentUITransform.anchorX = 0;
            } else {
                this.contentUITransform.anchorX = 1;
            }
        }
    }

    protected onDestroy(): void {
        this.itemMap.forEach((item) => {
            item.destroy();
        })
        this.itemMap.clear();
        this.itemPool.forEach(pool => {
            pool.forEach(item => {
                item.destroy();
            })
        })
        this.itemPool.clear();
    }

    private getItemPrefabIndex(index: number) {
        return this.getItemResIndex?.(index) || 0;
    }

    /**通过数据下标获取对应预制体*/
    private getItemPrefabByIndex(index: number) {
        let prefabIndex = this.getItemPrefabIndex(index);
        let prefab = this.itemPrefabs[prefabIndex];
        if (!prefab) {
            console.error("VirtualList 预制体数组中没有对应索引的预制体");

        }
        return prefab;
    }

    /**
     * 设置数据
     */
    setData(dataList: any[]) {
        this.recycle();
        this.dataList = dataList;
        if (this.layoutType == Layout.Type.GRID) {
            this.calcGridLayout();
        } else if (this.layoutType == Layout.Type.VERTICAL) {
            if (this.autoSizeItem) {
                this.calculateAutoItem();
            } else {
                this.calcVerticalLayout();
            }
        } else if (this.layoutType == Layout.Type.HORIZONTAL) {
            this.calcHorizontalLayout();
        }
        this.onScrolling();
    }

    /**
     * 网格布局计算
     */
    private calcGridLayout() {
        let prefab = this.itemPrefabs[0];
        this.itemPositions = [];
        const contentWidth = this.contentUITransform.width;
        const contentHeight = this.contentUITransform.height;
        const itemW = prefab.data.width;
        const itemH = prefab.data.height;

        if (this.vertical) {
            this.columnCount = Math.max(1, Math.floor((contentWidth - this.paddingLeft - this.paddingRight + this.spacingX) / (itemW + this.spacingX)));
            const totalRows = Math.ceil(this.dataList.length / this.columnCount);
            const totalHeight = totalRows * itemH + (totalRows - 1) * this.spacingY + this.paddingTop + this.paddingBottom;
            this.contentUITransform.height = Math.max(totalHeight, this.scrollViewUITransform.height);
            let dir = this.verticalDirection == Layout.VerticalDirection.TOP_TO_BOTTOM ? -1 : 1;
            for (let i = 0; i < this.dataList.length; i++) {
                const row = Math.floor(i / this.columnCount);
                const col = i % this.columnCount;

                const x = -contentWidth / 2 + this.paddingLeft + itemW / 2 + col * (itemW + this.spacingX);
                let y = -this.paddingTop - itemH / 2 - row * (itemH + this.spacingY);
                y *= -dir;
                this.itemPositions.push(new Vec3(x, y, 0));
            }
        }
        else {
            const rowCount = Math.max(1, Math.floor((contentHeight - this.paddingTop - this.paddingBottom + this.spacingY) / (itemH + this.spacingY)));
            const totalCols = Math.ceil(this.dataList.length / rowCount);
            const totalWidth = totalCols * itemW + (totalCols - 1) * this.spacingX + this.paddingLeft + this.paddingRight;
            this.contentUITransform.width = Math.max(totalWidth, this.scrollViewUITransform.width);
            const dir = this.horizontalDirection === Layout.HorizontalDirection.LEFT_TO_RIGHT ? 1 : -1;
            for (let i = 0; i < this.dataList.length; i++) {
                const col = Math.floor(i / rowCount);
                const row = i % rowCount;
                const x = dir * (this.paddingLeft + itemW / 2 + col * (itemW + this.spacingX));
                const y = this.contentUITransform.height / 2 + -this.paddingTop - itemH / 2 - row * (itemH + this.spacingY);
                this.itemPositions.push(new Vec3(x, y, 0));
            }
        }
    }

    /**垂直布局计算 */
    private calcVerticalLayout() {
        this.itemPositions = [];
        let y = this.paddingTop;
        let lastY = 0;
        let dir = this.verticalDirection == Layout.VerticalDirection.TOP_TO_BOTTOM ? -1 : 1;
        for (let i = 0; i < this.dataList.length; i++) {
            let prefab = this.getItemPrefabByIndex(i);
            const height = prefab.data.height;
            y += lastY / 2 + height / 2 + this.spacingY;
            lastY = height;
            this.itemPositions.push(new Vec3(0, y * dir, 0));
        }
        y += lastY / 2 + this.paddingBottom;
        this.content.getComponent(UITransform)!.height = Math.max(y, this.scrollView.node.getComponent(UITransform)!.height);
    }

    private calcHorizontalLayout() {
        this.itemPositions = [];
        let x = this.paddingLeft;
        let lastX = 0;
        for (let i = 0; i < this.dataList.length; i++) {
            let prefab = this.getItemPrefabByIndex(i);;
            const width = prefab.data.width;
            x += lastX / 2 + width / 2 + this.spacingX;
            lastX = width;
            let dir = this.horizontalDirection == Layout.HorizontalDirection.LEFT_TO_RIGHT ? 1 : -1;
            this.itemPositions.push(new Vec3(x * dir, 0, 0));
        }
        x += lastX / 2 + this.paddingRight;
        this.content.getComponent(UITransform)!.width = Math.max(x, this.scrollView.node.getComponent(UITransform)!.width);
    }

    /**
     * 滚动时动态创建/回收节点
     */
    private onScrolling() {
        let scrollVieWidth = this.scrollViewUITransform.width;
        let scrollVieHeight = this.scrollViewUITransform.height;

        let contentX = this.content.x;
        let contentY = this.content.y;
        //视图外缓冲区可见大小   可优化点,判断滚动方向, 如往下滚动时,当前item在滚动视图下方时且不在可视区,那么他后面的item也不再,可以直接跳出循环
        let buffer = 200;
        for (let i = 0; i < this.dataList.length; i++) {
            let itemWidth = 0;
            let itemHeight = 0;
            if (this.autoSizeItem) {
                itemWidth = this.itemSizes[i].width;
                itemHeight = this.itemSizes[i].height;
            } else {
                let item = this.getItemPrefabByIndex(i);
                itemWidth = item.data.width;
                itemHeight = item.data.height;
            }
            const pos = this.itemPositions[i];
            let localX = pos.x + contentX;
            let localY = pos.y + contentY;

            let outView = localX + itemWidth / 2 + buffer < -scrollVieWidth / 2
                || localX - itemWidth / 2 - buffer > scrollVieWidth / 2
                || localY + itemHeight / 2 + buffer < -scrollVieHeight / 2
                || localY - itemHeight / 2 - buffer > scrollVieHeight / 2;
            if (outView) {
                this.recycleItemByIndex(i);
            } else {
                this.updateItemByIndex(i);
            }
        }
    }

    private updateItemByIndex(index: number) {
        let node = this.itemMap.get(index);
        if (!node) {
            //在列表的不需要更新
            let type = this.getItemResIndex(index);
            if (!this.itemPool.has(type)) this.itemPool.set(type, []);
            const pool = this.itemPool.get(type)!;
            node = pool.length > 0 ? pool.pop()! : this.instantiateByType(index);
            node.setParent(this.content);
            const pos = this.itemPositions[index];
            node.setPosition(pos);
            this.itemMap.set(index, node);
            this.itemRender?.(index, node);
            this.itemTypeMap.set(node.uuid, type);
        }
    }

    private recycleItemByIndex(index: number) {
        const node = this.itemMap.get(index)!;
        if (!node) {
            return;
        }
        let type = this.itemTypeMap.get(node.uuid);
        this.itemMap.delete(index);
        node.removeFromParent();

        if (!this.itemPool.has(type)) this.itemPool.set(type, []);
        this.itemPool.get(type)!.push(node);
    }

    private recycle() {
        for (const [index, node] of this.itemMap) {
            this.recycleItemByIndex(index);
        }
        this.itemMap.clear();
        this.itemPositions = [];
        this.itemSizes = [];
        this.itemPool.forEach(pool => pool.forEach(n => n.removeFromParent()));
    }

    private instantiateByType(index: number): Node {
        let prefab = this.itemPrefabs[this.getItemResIndex?.(index) || 0];
        let node = instantiate(prefab);
        if (this.onItemClick) {
            node.on("click", this.clickItem, this);
        }
        return node;
    }

    /**
     * 获取指定 index 对应的 item 节点（在屏幕内时返回，不可见返回 null）
     */
    public getItemNode(index: number): Node | null {
        return this.itemMap.get(index) ?? null;
    }

    private clickItem(button: Button) {
        this.itemMap.forEach((node: Node, index: number) => {
            if (button.node == node) {
                this.onItemClick(index, node);
            }
        })
    }

    /**
 * 滚动到指定 index 的 item（垂直/水平/网格皆适配）
 * @param index item 下标
 * @param duration 滚动动画时长（默认 0.3 秒）
 */
    public scrollToIndex(index: number, duration: number = 0.3) {
        if (index < 0 || index >= this.itemPositions.length) return;

        const targetPos = this.itemPositions[index];
        const viewSize = this.scrollViewUITransform;
        const contentSize = this.contentUITransform;

        let offset: Vec2;

        if (this.vertical) {
            const contentHeight = contentSize.height;
            const viewHeight = viewSize.height;

            const anchorOffset = contentHeight * (1 - contentSize.anchorY);
            const itemY = -targetPos.y + anchorOffset;

            // 中心偏移
            const scrollY = itemY - viewHeight / 2;
            const maxScrollY = contentHeight - viewHeight;
            const clampedY = Math.max(0, Math.min(scrollY, maxScrollY));

            offset = new Vec2(0, clampedY);
        } else if (this.horizontal) {
            const contentWidth = contentSize.width;
            const viewWidth = viewSize.width;

            const anchorOffset = contentWidth * contentSize.anchorX;
            const itemX = targetPos.x + anchorOffset;

            const scrollX = itemX - viewWidth / 2;
            const maxScrollX = contentWidth - viewWidth;
            const clampedX = Math.max(0, Math.min(scrollX, maxScrollX));

            offset = new Vec2(clampedX, 0);
        }

        this.scrollView.scrollToOffset(offset, duration);
    }

    private calculateAutoItem() {
        this.itemPositions = [];
        let y = 0 + this.paddingTop;
        let lastY = 0;
        let dir = this.verticalDirection == Layout.VerticalDirection.TOP_TO_BOTTOM ? -1 : 1;

        //创建item加入场景=>设置item数据,更新itemLayout 回收
        for (let i = 0; i < this.dataList.length; i++) {
            let prefabIndex = this.getItemPrefabIndex(i);

            let node: Node = null;
            let pool = this.itemPool.get(prefabIndex);
            if (!pool) {
                pool = [];
                this.itemPool.set(prefabIndex, pool);
            }
            if (pool.length) {
                node = pool.pop();
            } else {
                let prefab = this.getItemPrefabByIndex(i);
                node = instantiate(prefab);
            }

            this.scrollView.node.parent.addChild(node);
            //手动更新渲染组件 获取大小 
            let comp = node.getComponent(VirtualItem);
            if (!comp) {
                throw new Error("不固定大小的预制体请挂载组件 继承VirtualItem");
                return;
            }
            comp.setData(i, this.dataList[i]);
            node.getComponentsInChildren(Label).forEach(e => {
                e.updateRenderData(true);
            });
            node.getComponentsInChildren(Layout).forEach(e => {
                e.updateLayout(true);
            });
            node.getComponent(Layout).updateLayout(true);

            let transForm = node.getComponent(UITransform);
            const height = transForm.height
            const width = transForm.width;

            this.itemSizes.push({ width, height });
            y += lastY / 2 + height / 2 + this.spacingY;
            lastY = height;
            this.itemPositions.push(new Vec3(0, y * dir, 0));
            node.removeFromParent();
            pool.push(node);
        }
        y += lastY / 2 + this.paddingBottom;
        this.content.getComponent(UITransform)!.height = Math.max(y, this.scrollView.node.getComponent(UITransform)!.height);
    }

    public scrollToTop(timeInSconds?: number) {
        this.scrollView.stopAutoScroll()
        this.scrollView.scrollToTop(timeInSconds);
        if (!timeInSconds) {
            this.onScrolling();
        }
    }

    public scrollToBottom(timeInSconds?: number) {
        this.scrollView.stopAutoScroll();
        this.scrollView.scrollToBottom(timeInSconds);
        if (!timeInSconds) {
            this.onScrolling();
        }
    }

    public scrollToLeft(timeInSconds?: number) {
        this.scrollView.stopAutoScroll();
        this.scrollView.scrollToLeft(timeInSconds);
        if (!timeInSconds) {
            this.onScrolling();
        }
    }

    public scrollToRight(timeInSconds?: number) {
        this.scrollView.stopAutoScroll();
        this.scrollView.scrollToRight(timeInSconds);
        if (!timeInSconds) {
            this.onScrolling();
        }
    }
}
